package pfc;

import java.io.IOException;
import java.io.*;
import java.util.LinkedList;

import easypastry.cast.CastHandler;
import easypastry.core.PastryConnection;
import easypastry.core.PastryKernel;
import easypastry.dht.DHTException;
import easypastry.dht.DHTHandler;
//import easypastry.sample.*;

import rice.p2p.commonapi.NodeHandle;

import pfc.*;
import pfc.mensaje.*;
//import cliente.Objeto;


/**
 * Implementa una interfaz de EasyPastry ofreciendo las funcionalidades que necesita la aplicación.
 * @author Gabriel Reus Rodríguez
 * @version 1.0, 06/11/2010
 */


public class Cliente
{

    private PastryConnection conn;

    private DHTHandler dht;

    private CastHandler cast;

    private String contexto;

    private String grupo;

    private boolean conectado=false;
	/**
	 * Inicializa el KBR.
	 */
    public void initKBR(String filename) throws Exception {

        PastryKernel.init(filename);

        conn = PastryKernel.getPastryConnection();

    }

	/**
	 * Inicializa el Hash dinámico.
	 */
    public void initDHT() throws DHTException {
	//Función que inicializa el hash dinamico.
        dht = PastryKernel.getDHTHandler(this.getContexto());

    }
	/**
	 * Inicializa el Cast.
	 */

    public void initCast(Aplicacion a) {
	//Función que inicializa el cast
        cast = PastryKernel.getCastHandler();
        cast.subscribe(this.getGrupo());
        cast.addDeliverListener(this.getGrupo(), new Listener(a));


    }
	/**
	 * Inicializa el nodo creado.
	 */

    public void start() throws Exception {
       	//Función que inicia los nodos creados.
            conn.bootNode();


    }  

    public DHTHandler getDHTHandler() {

        return dht;

    }

    public CastHandler getCastHandler() {

        return cast;

    }

    public PastryConnection getConnection()
    {
	return this.conn;
    }

    public boolean estaConectado()
    {
		return this.conectado;
    }

    public void setConexion(boolean b)
    {
		this.conectado=b;
    }

	/**
	 * Obtiene una lista de nodos conectados en el anillo.
	 */

    public LinkedList listaNodos()
    {
	//Función que obtiene la lista de vecinos del nodo.
//		LinkedList lista= new LinkedList<rice.p2p.commonapi.NodeHandle>(this.getCastHandler().getChildren(grupo));
		LinkedList lista= new LinkedList<rice.p2p.commonapi.NodeHandle>(this.getCastHandler().getNeighbours(1000,true));
		return lista;
    }

	/**
	 * Elimina el nodo local y desconecta a la aplicación del anillo.
	 */

    public  void eliminarNodo()
    {
	//Función que elimina el nodo local y nos desconecta del anillo.
		this.getCastHandler().unsubscribe(this.getGrupo());
		this.getCastHandler().close();
		this.conn.close();
		PastryKernel.close();
		
		this.conn=null;
		this.dht=null;
		this.cast=null;

    }

	/**
	 * Obtiene el valor de una clave en el Hash Dinámico.
	 */

    public String getClave(String clave) throws Exception
    {
	String valor="";
	try
	{
		DHTHandler hash_dinamico = this.getDHTHandler();
		valor=(String) hash_dinamico.get(clave);

	}
	catch(easypastry.dht.DHTException e)
	{
		throw new Exception("Error al obtener la clave: "+clave);
	}
	catch(Exception e)
	{
		throw new Exception("Excepcion desconocida");
	}
	if(valor == null)
	{
		valor="";
	}
	return valor;
    }

	/**
	 * Introduce un valor en el Hash Dinámico y lo asocia a una clave.
	 */

    public void putClave(String clave, String valor) throws Exception
    {
	//Función que introduce un valor en una clave del hash dinamico.
	try
	{
		DHTHandler hash_dinamico = this.getDHTHandler();
		hash_dinamico.put(clave,valor);
	}
	catch(easypastry.dht.DHTException e)
	{
		throw new Exception("Error al guardar la clave: "+clave+" y el valor: "+valor);
	}

    }

	/**
	 * Elimina una clave (y su valor) del Hash Dinámico.
	 */

    public void removeClave(String clave) throws Exception
    {
	//Función que elimina una clave del hash dinamico.
	try
	{
		DHTHandler dht = this.getDHTHandler();
		dht.remove(clave);
	}
	catch(easypastry.dht.DHTException e)
	{
		throw new Exception("Error al eliminar la clave: "+clave);
	}


    }

	/**
	 * Obtiene el nodo con identificador id.
	 */

   public  NodeHandle obtenNodo(String id) throws Exception
    {
	//Función que obtiene el nodo correspondiente a la id que le pasamos por parametro.
		LinkedList lista= this.listaNodos();
		for(int i=0;i<lista.size();i++)
		{
			NodeHandle aux= (NodeHandle)lista.get(i);
			if(id.equals(aux.getId().toStringFull()))
			{
				//Nodo encontrado!;
				return aux;
			}
		}
		throw new Exception("No se encontró el nodo solicitado");

    }
	/**
	 * Envía un mensaje a un destinatario en concreto.
	 */

    public void enviaObjeto(String id, Mensaje obj) throws Exception
    {
	//Función que envia un objeto a un destinatario.
	NodeHandle nodo= null;
	try
	{
		nodo=this.obtenNodo(id);
	}
	catch(Exception e)
	{
		throw e;
	}
	CastHandler cast = this.getCastHandler();
        cast.sendDirect(nodo, obj);

    }

	/**
	 * Envía un mensaje vía multicast.
	 */

    public void enviaObjetoMultiCast(Mensaje obj) throws Exception
    {
	//Función que envía un  objeto a todos los usuarios via multicast.
	        cast.sendMulticast(this.getGrupo(), obj);	

    }

	/**
	 * Obtiene la id del nodo local.
	 */

    public String obtenIdNodo()
    {
	//Función que obtiene la id del nodo local
	return this.cast.getLocalNodeHandle().getId().toStringFull();

    }

	/**
	 * Nos dice si es root.
	 */

    public boolean esRoot()
    {
		return this.getCastHandler().isRoot(this.getGrupo());
    }

	/**
	 * Función para testear el funcionamiento del Hash Dinámico.
	 */

    public void testDHT() throws DHTException {

        DHTHandler dht = getDHTHandler();

        System.out.println("DHT | Inserting : <key1, value1>");

        dht.put("key1", "value1");

        System.out.println("DHT | Inserting : <key1, value2>");

        dht.put("key1", "value2");

        System.out.println("DHT | Retrieving : <key1>");

        String value = (String) dht.get("key1");

        System.out.println("DHT | Current value of <key1> : <"+value+">");

    }
	/**
	 * Función para testear el funcionamiento del Cast.
	 */

    public void testCast() {

        CastHandler cast = getCastHandler();

        System.out.println("Cast | Direct : text message 1");

        cast.sendDirect(cast.getLocalNodeHandle(), new Texto("Mensaje directo",this.getGrupo()));

//        cast.sendDirect(PastryKernel.getNodeHandle(), new Objeto("text message 1","hola"));

        System.out.println("Cast | Muticast : text message 2");

        cast.sendMulticast(grupo, new Texto("text message 2",this.getGrupo()));

    }

    public String getGrupo()
    {
	return this.grupo;
    }

    public void setGrupo(String grupo)
    {
         this.grupo=grupo;
    }

    public String getContexto()
    {
	return this.contexto;
    }

    public void setContexto(String contexto)
    {
         this.contexto=contexto;
    }

}
